package tp01;

import tp01.utilitarios.CRC16;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import tp01.ui.Observador;
import tp01.utilitarios.ByteAndConversionsUtils;

public abstract class RequestCommand {

    private int nroDispositivo;
    private int nroFuncion;
    private int direccion;
    public boolean lecturaExitosa;
    private int lengthPDU;

    protected RequestCommand(int nroDispositivo, int nroFuncion, int direccion) {
        this.nroDispositivo = nroDispositivo;
        this.nroFuncion = nroFuncion;
        this.direccion = direccion;
    }

    public synchronized void escribir(byte[] adu) throws IOException {
        ConnectionController.outputStream.write(adu);
        ConnectionController.outputStream.flush();

    }

    public void ejecutar() {

        boolean result = true;

        List<byte[]> aduList = armarADU();

        Leedor leedor;
        for (byte[] adu : aduList) {

            new Escritor(this, adu).run();

            leedor = new Leedor();
            leedor.run();
            result &= leedor.lecturaExitosa;
        }

        Observador.getInstance().publicar();

        lecturaExitosa = result;

    }

    public abstract List<byte[]> armarPDU();

    private byte[] armarCabecera(boolean isTCP_IP) {

        byte[] result;

        if (!isTCP_IP) {
            result = new byte[1];
            result[0] = (byte) nroDispositivo;
        } else {

            result = new byte[7];

            /**
             * Transaction Identifier
             */
            //TODO
            result[0] = (byte) 1;
            result[1] = (byte) 2;

            /** Protocol identifier:
             *  MODBUS protocol = 0; 
             */
            result[2] = (byte) 0;
            result[3] = (byte) 0;

            /**
             * Length             
             */
            byte[] lengthB = ByteAndConversionsUtils.charToByteArray((char) (lengthPDU + 1));
            result[4] = (byte) lengthB[0];
            result[5] = (byte) lengthB[1];

            /**
             * Unit Identifier
             */
            //TODO
            result[6] = (byte) 1;

        }

        return result;
    }

    public List<byte[]> armarADU() {

        List<byte[]> result = new ArrayList<byte[]>();

        byte[] tramaAduActual = null;

        List<byte[]> pduList = armarPDU();

        for (byte[] pdu : pduList) {

            lengthPDU = pdu.length;

            //TODO
            if (true) {
                tramaAduActual = new byte[lengthPDU + 7];
            } else {
                tramaAduActual = new byte[lengthPDU + 3];
            }
            //TODO
            byte[] cabecera = armarCabecera(true);

            int i = 0;

            for (byte currentByte : cabecera) {
                tramaAduActual[i] = currentByte;
                i++;
            }

            for (byte b : pdu) {
                tramaAduActual[i] = b;
                i++;
            }

            //TODO
            if (false) {
                int[] crc = CRC16.calculateCRC(tramaAduActual, 0, tramaAduActual.length - 2);

                tramaAduActual[i] = (byte) crc[0];
                i++;
                tramaAduActual[i] = (byte) crc[1];
            }
            List<Integer> tramaInt = new ArrayList<Integer>();

            for (int b : tramaAduActual) {
                tramaInt.add(b);
            }

            Observador.getInstance().getConsola().append("###########\nRequest:\n");

            RequestCommand.mostrarTrama(tramaInt, ConnectionController.FORMATO_DECIMAL, false);
            RequestCommand.mostrarTrama(tramaInt, ConnectionController.FORMATO_HEXADECIMAL, false);
            RequestCommand.mostrarTrama(tramaInt, ConnectionController.FORMATO_BINARIO, false);

            Observador.getInstance().getConsola().append("\n");

            result.add(tramaAduActual);

        }

        return result;
    }

    public static int[] leerTramaEntrante() throws IOException {

        boolean lecturaExitosa = true;

        List<Integer> palabrasEntrantes = new ArrayList<Integer>();

        StringBuilder readBuffer = new StringBuilder();

        int c = -1;
        int i = 0;

        int cantidadDeBytes = 255;
        int functionOrErrorCode = 0;

        long startTimeStamp = new Date().getTime();
        boolean terminar = false;

        //TODO arreglar para serial while (!terminar && i < cantidadDeBytes + 3) {
        while (!terminar && i < cantidadDeBytes + 7) {

            while (ConnectionController.inputStream.available() == 0) {
                if (startTimeStamp + ConnectionController.timeout < new Date().getTime()) {
                    terminar = true;
                    break;
                }
            }

            if (!terminar && (c = ConnectionController.inputStream.read()) != -1) {

                readBuffer.append(c);

                //TODO
                if (true) {
                    switch (i) {
                        case 7:

                            functionOrErrorCode = c;

                            if (functionOrErrorCode > 0x80) {
                                cantidadDeBytes = 2;
                            }

                            break;
                        case 8:

                            if (functionOrErrorCode == 16) {
                                cantidadDeBytes = 5;
                            } else if (functionOrErrorCode == 6) {
                                cantidadDeBytes = 5;
                            } else if (functionOrErrorCode == 3) {
                                cantidadDeBytes = c + 2;
                            } else if (functionOrErrorCode > 0x80) {

                                System.out.println("Fallo al ejecutar la funcion: " + (functionOrErrorCode - 128));
                                System.out.println("Exception Code: " + c);
                            }
                            break;
                    }
                } else {
                    switch (i) {
                        case 1:

                            functionOrErrorCode = c;

                            if (functionOrErrorCode > 0x80) {
                                cantidadDeBytes = 2;
                            }

                            break;
                        case 2:

                            if (functionOrErrorCode == 16) {
                                cantidadDeBytes = 5;
                            } else if (functionOrErrorCode == 6) {
                                cantidadDeBytes = 5;
                            } else if (functionOrErrorCode == 3) {
                                cantidadDeBytes = c + 2;
                            } else if (functionOrErrorCode > 0x80) {

                                System.out.println("Fallo al ejecutar la funcion: " + (functionOrErrorCode - 128));
                                System.out.println("Exception Code: " + c);
                            }
                            break;
                    }
                }
                palabrasEntrantes.add(c);

                i++;
            }
        }

        if (!terminar) {

            if (c == -1) {
                throw new RuntimeException("Error en la lectura.");
            }

            Observador.getInstance().getConsola().append("Variables:\n");

            if (functionOrErrorCode == 3) {

                int currentValue = 0;

                byte[] currentVariable = null;
                
                // TODO arreglar para serial for (int j = 3; j < cantidadDeBytes + 3 - 2; j = j + 2) {
                 
                
                for (int j = 9; j < 11; j = j + 2) {
                    currentVariable = new byte[2];
                    currentVariable[0] = (byte) palabrasEntrantes.get(j).intValue();
                    currentVariable[1] = (byte) palabrasEntrantes.get(j + 1).intValue();

                    currentValue = (int) ByteAndConversionsUtils.byteArrayToChar(currentVariable);

                    Observador.getInstance().getConsola().append(j / 2);
                    Observador.getInstance().getConsola().append(": ");
                    Observador.getInstance().getConsola().append(currentValue);
                    Observador.getInstance().getConsola().append("\n");
                }

                Observador.getInstance().getConsola().append("\n");

            }

            ConnectionController.timeStamp = new java.util.Date().getTime();

            Observador.getInstance().getConsola().append("Response:\n");

            mostrarTrama(palabrasEntrantes, ConnectionController.FORMATO_DECIMAL, false);
            mostrarTrama(palabrasEntrantes, ConnectionController.FORMATO_HEXADECIMAL, false);
            mostrarTrama(palabrasEntrantes, ConnectionController.FORMATO_BINARIO, false);

            Observador.getInstance().getConsola().append("\n");
            Observador.getInstance().publicar();

            int[] result = new int[palabrasEntrantes.size()];
            i = 0;
            for (int currentC : palabrasEntrantes) {
                result[i] = currentC;
                i++;
            }

            byte[] resultByte = new byte[result.length];
            i = 0;
            for (int currentC : result) {
                resultByte[i] = (byte) currentC;
                i++;
            }
            int expectedCRC[] = CRC16.calculateCRC(resultByte, 0, result.length - 2);
            int k = result.length - 2;

            boolean validacionCRC = (result[k] == expectedCRC[0]
                    && result[k + 1] == expectedCRC[1]);

            System.out.println("CRC Valido: " + validacionCRC);

            if (!validacionCRC) {
                lecturaExitosa = false;
            }

            if (!lecturaExitosa) {
                return null;
            }

            return result;

        }

        return null;


    }

    public static void mostrarTrama(List<Integer> palabrasEntrantes, short formato, boolean publicar) {

        List<String> tramaParaMostrar = new ArrayList<String>();

        switch (formato) {
            case ConnectionController.FORMATO_DECIMAL:
                Observador.getInstance().getConsola().append("-->Formato Decimal: ");
                for (int b : palabrasEntrantes) {
                    tramaParaMostrar.add(Integer.toString(b));
                }
                break;
            case ConnectionController.FORMATO_BINARIO:
                Observador.getInstance().getConsola().append("-->Formato Binario: ");
                for (int b : palabrasEntrantes) {
                    tramaParaMostrar.add(Integer.toBinaryString(b));

                }
                break;
            case ConnectionController.FORMATO_HEXADECIMAL:
                Observador.getInstance().getConsola().append("-->Formato Hexadecimal: ");
                for (int b : palabrasEntrantes) {
                    tramaParaMostrar.add(Integer.toHexString(b));
                }
                break;
        }

        Observador.getInstance().getConsola().append(tramaParaMostrar);
        Observador.getInstance().getConsola().append("\n");

        if (publicar) {
            Observador.getInstance().publicar();
        }
    }

    /**
     * @return the nroDispositivo
     */
    public int getNroDispositivo() {
        return nroDispositivo;
    }

    /**
     * @param nroDispositivo the nroDispositivo to set
     */
    public void setNroDispositivo(int nroDispositivo) {
        this.nroDispositivo = nroDispositivo;
    }

    /**
     * @return the nroFuncion
     */
    public int getNroFuncion() {
        return nroFuncion;
    }

    /**
     * @param nroFuncion the nroFuncion to set
     */
    public void setNroFuncion(int nroFuncion) {
        this.nroFuncion = nroFuncion;
    }

    /**
     * @return the direccion
     */
    public int getDireccion() {
        return direccion;
    }

    /**
     * @param direccion the direccion to set
     */
    public void setDireccion(int direccion) {
        this.direccion = direccion;
    }
}
